#ifndef X64EMITTER_H
#define X64EMITTER_H

#include <stdint.h>
#include <assert.h>



// ----------------------------------------------

typedef enum
{
    XMM0 = 0,
    XMM1 = 1,
    XMM2 = 2,
    XMM3 = 3,
    XMM4 = 4,
    XMM5 = 5,
    XMM6 = 6,
    XMM7 = 7,
    XMM8 = 8,
    XMM9 = 9,
    XMM10 = 10,
    XMM11 = 11,
    XMM12 = 12,
    XMM13 = 13,
    XMM14 = 14,
    XMM15 = 15,

} XMMRegister;

// ----------------------------------------------

class MemoryAddress
{
public:
    MemoryAddress(int nValue, int nOffset, int nMul, int32_t imm)
    {
        id = nValue;
        idOffset = nOffset;
        mul = nMul;
        immOffset = imm;
    }

public:
    int id;
    int idOffset;
    int mul;
    int32_t immOffset;
};

// ----------------------------------------------

class GPRegister
{
public:

    GPRegister(int nValue)
    {
        id = nValue;
        idOffset = -1;
        mul = 0;
        immOffset = 0;
    }
    GPRegister(int nValue, int nOffset, int nMul, int32_t imm)
    {
        id = nValue;
        idOffset = nOffset;
        mul = nMul;
        immOffset = imm;
    }
    ~GPRegister();

    GPRegister operator+(const GPRegister& right) const;
    GPRegister operator+(const int& right) const;
    GPRegister operator-(const int& right) const;
    GPRegister operator*(const int& nMul) const;
    MemoryAddress operator*() const;
    bool operator<(const GPRegister& right) const
        { return (id < right.id); }
    bool operator>(const GPRegister& right) const
        { return (id > right.id); }
    bool operator<=(const GPRegister& right) const
        { return (id <= right.id); }
    bool operator>=(const GPRegister& right) const
        { return (id >= right.id); }
    bool operator==(const GPRegister& right) const
        { return (id == right.id); }

public:
    int id;
    int idOffset;
    int mul;
    int32_t immOffset;

};

// ----------------------------------------------

const GPRegister RAX = GPRegister(0);
const GPRegister RCX = GPRegister(1);
const GPRegister RDX = GPRegister(2);
const GPRegister RBX = GPRegister(3);
const GPRegister RSP = GPRegister(4);
const GPRegister RBP = GPRegister(5);
const GPRegister RSI = GPRegister(6);
const GPRegister RDI = GPRegister(7);
const GPRegister R8  = GPRegister(8);
const GPRegister R9  = GPRegister(9);
const GPRegister R10 = GPRegister(10);
const GPRegister R11 = GPRegister(11);
const GPRegister R12 = GPRegister(12);
const GPRegister R13 = GPRegister(13);
const GPRegister R14 = GPRegister(14);
const GPRegister R15 = GPRegister(15);

// ----------------------------------------------

typedef uint8_t* Label;
typedef int32_t* JMP_Handle;

// ----------------------------------------------

class x64Emitter
{

public:
    x64Emitter(void* pvBuffer, int nSize);
    ~x64Emitter();

private:
    uint8_t* m_pbBuffer;
    int      m_nBufferSize;

    uint8_t* m_pbWrite;

private:

    template<typename opcode_t>
    void instructionRegToReg(const GPRegister& rd, const GPRegister& rs, opcode_t opcode);

    template<typename opcode_t>
    void instructionMemToReg(const GPRegister& rd, const MemoryAddress& mem, opcode_t opcode);


    void immediateGroup1(const GPRegister& rd, const int32_t& imm32, uint8_t opcodeExtend);
    void group3(const GPRegister& rd, uint8_t opcodeExtend);

    void jumpBack(const Label& label, uint8_t op8, uint16_t op32);
    JMP_Handle jumpFuture(uint16_t op32);

    // ----------------------------------------------

public:

#define MEMORY_INSTRUCTION(name) \
    void name(const GPRegister& rd, const GPRegister& rs); \
    void name(const GPRegister& rd, const MemoryAddress& mem); \
    void name(const MemoryAddress& mem, const GPRegister& rd)

    MEMORY_INSTRUCTION(MOV);
    MEMORY_INSTRUCTION(ADD);
    MEMORY_INSTRUCTION(ADC);
    MEMORY_INSTRUCTION(AND);
    MEMORY_INSTRUCTION(XOR);
    MEMORY_INSTRUCTION(OR);
    MEMORY_INSTRUCTION(SBB);
    MEMORY_INSTRUCTION(SUB);
    MEMORY_INSTRUCTION(CMP);
    MEMORY_INSTRUCTION(TEST);
    MEMORY_INSTRUCTION(XCHG);

#undef MEMORY_INSTRUCTION

    void MOV(const GPRegister& rd, const int64_t& imm64);
    void MOV(const GPRegister& rd, const int32_t& imm32);

    void ADD(const GPRegister& rd, const int32_t& imm32);
    void OR (const GPRegister& rd, const int32_t& imm32);
    void ADC(const GPRegister& rd, const int32_t& imm32);
    void SBB(const GPRegister& rd, const int32_t& imm32);
    void AND(const GPRegister& rd, const int32_t& imm32);
    void SUB(const GPRegister& rd, const int32_t& imm32);
    void XOR(const GPRegister& rd, const int32_t& imm32);
    void CMP(const GPRegister& rd, const int32_t& imm32);

    void LEA (const GPRegister& rd, const MemoryAddress& mem);
    void IMUL(const GPRegister& rd, const MemoryAddress& mem);
    void IMUL(const GPRegister& rd, const GPRegister& rs);
    void IMUL(const GPRegister& rd, const MemoryAddress& mem, int32_t imm32);
    void IMUL(const GPRegister& rd, const GPRegister& rs, int32_t imm32);

    void NOT(const GPRegister& rd);
    void NEG(const GPRegister& rd);

    // ----------------------------------------------

    void PUSH(const GPRegister& rs);
    void POP(const GPRegister& rd);

    // ----------------------------------------------

    void LABEL(Label* label);
    void placeJump(const JMP_Handle& jump, const Label& label);

#define JUMP_INSTRUCTION(name) \
    void name(const Label& label); \
    JMP_Handle name()

    JUMP_INSTRUCTION(JA);
    JUMP_INSTRUCTION(JAE);
    JUMP_INSTRUCTION(JB);
    JUMP_INSTRUCTION(JBE);
    JUMP_INSTRUCTION(JC);
    JUMP_INSTRUCTION(JE);
    JUMP_INSTRUCTION(JG);
    JUMP_INSTRUCTION(JGE);
    JUMP_INSTRUCTION(JL);
    JUMP_INSTRUCTION(JLE);
    JUMP_INSTRUCTION(JNA);
    JUMP_INSTRUCTION(JNAE);
    JUMP_INSTRUCTION(JNB);
    JUMP_INSTRUCTION(JNBE);
    JUMP_INSTRUCTION(JNC);
    JUMP_INSTRUCTION(JNE);
    JUMP_INSTRUCTION(JNG);
    JUMP_INSTRUCTION(JNGE);
    JUMP_INSTRUCTION(JNL);
    JUMP_INSTRUCTION(JNLE);
    JUMP_INSTRUCTION(JNO);
    JUMP_INSTRUCTION(JNP);
    JUMP_INSTRUCTION(JNS);
    JUMP_INSTRUCTION(JNZ);
    JUMP_INSTRUCTION(JO);
    JUMP_INSTRUCTION(JP);
    JUMP_INSTRUCTION(JPE);
    JUMP_INSTRUCTION(JPO);
    JUMP_INSTRUCTION(JS);
    JUMP_INSTRUCTION(JZ);
    JUMP_INSTRUCTION(JMP);
    JUMP_INSTRUCTION(CALL);

#undef JUMP_INSTRUCTION

};


#endif
